#include <reg51.h>
#include <math.h>
#include <INTRINS.H>

#define uchar unsigned char
#define uint unsigned int

// 定义端口
sbit led1 = P1^3;       // 温度超出范围指示灯
sbit led2 = P1^4;
sbit buzzer = P3^3;     // 蜂鸣器引脚
sbit DQ = P3^7;         // DS18B20 端口
sbit RS = P0^4;         // 1602 数据/命令选择端（H：数据寄存器L：指令寄存器） 
sbit RW = P0^5;         // 1602 读/写选择端 
sbit E = P0^6;          // 1602 使能信号端
sbit key1 = P3^4;       // 用户按键
sbit key2 = P3^5;
sbit key3 = P3^6;

// 全局变量
int temp;               // 测得温度
char temp_max = 30, temp_min = 20;  // 设定的温度 
char TempH, TempL;
uchar flag_get, num = 0;
uchar code tab[] = {'0','1','2','3','4','5','6','7','8','9'};  // 液晶显示字符
uchar tab1[] = "min:020  max:030";  // 液晶第二行显示内容
uchar  str[8];

// 延时函数
void delay1(uint i) {
    while(i--);
}

void delay(uint z) {
    uint x, y;
    for(x = z; x > 0; x--)
        for(y = 110; y > 0; y--);
}

// DS18B20 初始化
void Init_DS18B20(void) {
    uchar x = 0;
    DQ = 1;    // 拉高总线,等待
    delay1(8);
    DQ = 0;    // 单片机将DQ拉低
    delay1(80); // 精确延时 大于 480us
    DQ = 1;    // 拉高总线,等待
    delay1(10);
    x = DQ;   // 稍做延时后 如果x=0则初始化成功 x=1则初始化失败
    delay1(5);
}

// DS18B20 读一个字节
uchar ReadOneChar(void) {
    uchar i = 0;
    uchar dat = 0;
    for (i = 8; i > 0; i--) {
        DQ = 0; // 给脉冲信号
        dat >>= 1;
        DQ = 1; // 给脉冲信号，主机在读时隙期间必须释放总线
        if (DQ)
            dat |= 0x80;
        delay1(5);
    }
    return(dat);
}

// DS18B20 写一个字节
void WriteOneChar(uchar dat) {
    uchar i = 0;
    for (i = 8; i > 0; i--) {
        DQ = 0;
        DQ = dat & 0x01; // 取最低位
        delay1(5);
        DQ = 1;
        dat >>= 1; // 左移
    }
    delay1(5);
}

// DS18B20 读取温度
uint ReadTemperature(void) {
    uchar a = 0;
    uint b = 0;
    uint t = 0;
    Init_DS18B20();             // DS18B20 初始化
    WriteOneChar(0xCC);         // 跳过读序号列号的操作
    WriteOneChar(0x44);         // 启动温度转换
    delay1(200);                // 延时以求信号的稳定
    Init_DS18B20();             // DS18B20 再次初始化
    WriteOneChar(0xCC);         // 跳过读序号列号的操作 
    WriteOneChar(0xBE);         // 读取温度寄存器等（共可读9个寄存器） 前两个就是温度
    a = ReadOneChar();          // 低位
    b = ReadOneChar();          // 高位
    b <<= 8;                    // b 左移 8 位
    t = a + b;                  // 把 a 与 b 结合，放在16位的 t 中
    return(t);
}

// LCD1602 写命令操作
void WriteCommand(uchar com) {
    delay(5);    // 操作前短暂延时，保证信号稳定
    E = 0;
    RS = 0;
    RW = 0;
    P2 = com;
    E = 1;
    delay(5);
    E = 0;
}

// LCD1602 写数据操作
void WriteData(uchar dat) {
    delay(5);    // 操作前短暂延时，保证信号稳定
    E = 0;
    RS = 1;
    RW = 0;
    P2 = dat;
    E = 1;
    delay(5);
    E = 0;
}

// LCD1602 初始化程序
void InitLcd() {
    uchar i;
    delay(15);
    WriteCommand(0x38);   // display mode
    WriteCommand(0x06);   // 显示光标移动位置
    WriteCommand(0x0c);   // 显示开及光标设置
    WriteCommand(0x01);   // 显示清屏
    WriteCommand(0x80 + 0x40); // 将光标移到第二行
    for(i = 0; i < 16; i++) {  // 显示初始化内容
        WriteData(tab1[i]);
        delay(10);
    }
}

// 蜂鸣器报警程序
void alarm() {
    if (TempH >= temp_max) {   // 当温度高于设置的最高温报警
        buzzer = 0;
        delay(50);
        buzzer = 1;
        delay(50);
        led1 = 0;
        led2 = 1;
    } else if (TempH < temp_min) {  // 当温度低于设置的最低温报警
        buzzer = 0;
        delay(50);
        buzzer = 1;
        delay(50);
        led1 = 1;
        led2 = 0;    
    } else {    // 报警解除
        buzzer = 1;
        led1 = 1;
        led2 = 1;
    }
}

// 显示测得的温度
void handle_T() {
    uchar i;
    str[0] = 0x20;  // 显示为空
    str[1] = tab[TempH / 100];     // 百位温度
    str[2] = tab[(TempH % 100) / 10];  // 十位温度
    str[3] = tab[(TempH % 100) % 10];  // 个位温度,带小数点
    str[5] = tab[TempL];
    str[4] = '.';
    str[6] = 0xdf;
    str[7] = 'C';
    if (flag_get == 1) {       // 定时读取当前温度
        flag_get = 0;       // 清标志位
        temp = ReadTemperature(); // 读取温度值
        if (temp & 0x8000) {
            str[0] = 0xb0;  // 负号标志
            temp = ~temp;   // 取反加1
            temp += 1;
        }
        TempH = temp >> 4;  // 由权重表知移4位就是整数位
        TempL = temp & 0x0F;
        TempL = TempL * 6 / 10; // 小数近似处理
        alarm();    // 判断是否需要报警
    }
    WriteCommand(0x80 + 0x04); // 光标指向第一行第一个字符
    for(i = 0; i < 8; i++) {    // 显示
        WriteData(str[i]);
        delay(10);
    }
}

// 显示设定的温度范围
void display_range(uchar add, int t) {
    uchar i;
    if(t < 0) {
        str[0] = 0xb0;  // 负号标志
    } else {
        str[0] = tab[abs(t) / 100];    // 百位温度
        str[1] = tab[(abs(t) % 100) / 10]; // 十位温度
        str[2] = tab[(abs(t) % 100) % 10]; // 个位温度
        WriteCommand(0x80 + 0x40 + add);
    }

    for(i = 0; i < 3; i++) {
        WriteData(str[i]);
        delay(5);
    }
}

// 独立键盘扫描函数并显示设定的温度
void keyscan() { 
    uchar flag1 = 0, flag2 = 0; // 有按键按下标记
    P3 = 0xff;  // 拉高P3口，以读取P3口的值
    
    if (key1 == 0) {    // 两个按键控制最高/最低温度的加减 按住key1时通过其他2个键控制最高温度的加减
        delay(5);
        if (key2 == 0) {    // 最高温加
            delay(5);   // 延时消抖
            if (key2 == 0) {
                temp_max++;
                if (temp_max >= 85)
                    temp_max = 85;
            }
            while (!key2);  // 松手检测
            flag1 = 1;
        }
        if (key3 == 0) {    // 最高温减
            delay(5);
            if (key3 == 0) {
                temp_max--;
                if (temp_max <= temp_min)
                    temp_max = temp_min;
            }
            while (!key3);
            flag1 = 1;
        }
    }
    if (key1 == 1) {    // 松开key1时通过其他2个键控制最低温度的加减
        delay(5);
        if (key2 == 0) {    // 最低温加
            delay(5);   // 延时消抖
            if (key2 == 0) {
                temp_min++;
                if (temp_min >= temp_max)
                    temp_min = temp_max;
            }
            while (!key2);  // 松手检测
            flag2 = 1;
        }
        if (key3 == 0) {    // 最低温减
            delay(5);
            if (key3 == 0) {
                temp_min--;
                if (temp_min <= -10)
                    temp_min = -10;
            }
            while (!key3);
            flag2 = 1;
        }
    }
    if (flag1) {    // 如有设置最高温度的键按下，更新设定的温度
        flag1 = 0;  // 清标记
        display_range(0x0d, temp_max);
    }
    if (flag2) {    // 如有设置最低温度的键按下，更新设定的温度
        flag2 = 0;  // 清标记
        display_range(0x04, temp_min);
    }   
}

// 主函数
void main() {    
    TMOD |= 0x01;   // 定时器设置
    TH0 = 0xEF;     // 装初始值
    TL0 = 0xF0;    
    EA = 1;         // 开总中断
    ET0 = 1;        // 允许定时器0中断
    TR0 = 1;        // 开定时器0中断
    InitLcd();      // lcd1602 初始化
    flag_get = 1;
    while (1) {
        handle_T(); // 处理温度：获得、显示、报警
        keyscan();  // 独立按键扫描
    } 
}
// 定时0中断处理程序，用于温度检测间隔，大约1秒测一次温度
void timer0(void) interrupt 1 using 1 {
    TH0 = 0xEF;     // 定时器重装值
    TL0 = 0xF0;
    num++;
    if (num == 50) {
        num = 0;
        flag_get = 1;   // 标志位有效，开始测温
    }
}
